/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package bgu.dcr.tp.impl;

import bgu.dcr.tp.api.Behavior;
import bgu.dcr.tp.api.World;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Shl
 */
public abstract class Behaviors {

    private static ConcurrentHashMap<Class, Behavior[]> behaviorCache = new ConcurrentHashMap<>();
    private Behavior[] behaviors;
    private World world;
    private ThreadLocal<Integer> id = new ThreadLocal<>();

    public Integer getId() {
        return id.get();
    }

    public World getWorld() {
        return world;
    }

    public void setId(int id) {
        this.id.set(id);
    }

    public void setWorld(World world) {
        this.world = world;
    }

    public Behavior[] getBehaviors() {
        return behaviors;
    }

    public Behaviors() {
        behaviors = behaviorCache.get(getClass());
        if (behaviors == null) {
            List<ReflectedBehavior> behaviorMethods = new LinkedList<>();
            for (Method m : getClass().getMethods()) {
                MarkAsBehavior annotation = m.getAnnotation(MarkAsBehavior.class);
                if (annotation != null) {
                    m.setAccessible(true);
                    behaviorMethods.add(new ReflectedBehavior(m, annotation));
                }
            }

            behaviors = behaviorMethods.toArray(new Behavior[behaviorMethods.size()]);
            behaviorCache.put(getClass(), behaviors);
        }
    }

    private class ReflectedBehavior implements Behavior, Comparable<ReflectedBehavior>{

        private Method m;
        private Class[] requirement;
        private String name;

        public ReflectedBehavior(Method m, MarkAsBehavior annotation) {
            this.m = m;
            requirement = m.getParameterTypes();

            name = annotation.value();
            if (annotation.value().isEmpty()) {
                name = m.getName();
            }
        }

        @Override
        public void behave(int id, World world) throws Throwable {
            setId(id);
            setWorld(world);

            Object[] arguments = new Object[requirement.length];

            for (int i = 0; i < requirement.length; i++) {
                arguments[i] = world.getData(id, requirement[i]);
            }
            try {
                m.invoke(Behaviors.this, arguments);
            } catch (IllegalAccessException | IllegalArgumentException ex) {
                Logger.getLogger(Behaviors.class.getName()).log(Level.SEVERE, null, ex);
            } catch (InvocationTargetException ex) {
                throw ex.getCause();
            }
        }

        @Override
        public Class[] getRequirement() {
            return requirement;
        }

        @Override
        public String getName() {
            return name;
        }

        @Override
        public int compareTo(ReflectedBehavior t) {
            return name.compareTo(t.getName());
        }

        @Override
        public int hashCode() {
            int hash = 7;
            hash = 79 * hash + Objects.hashCode(this.m);
            hash = 79 * hash + Arrays.deepHashCode(this.requirement);
            hash = 79 * hash + Objects.hashCode(this.name);
            return hash;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final ReflectedBehavior other = (ReflectedBehavior) obj;
            if (!Objects.equals(this.m, other.m)) {
                return false;
            }
            if (!Arrays.deepEquals(this.requirement, other.requirement)) {
                return false;
            }
            if (!Objects.equals(this.name, other.name)) {
                return false;
            }
            return true;
        }
        
        
    }
}
